package com.ds.lens.data.service.metric;

import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.statement.SQLSelectQuery;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.sql.ast.statement.SQLSelectStatement;
import com.alibaba.fastjson.JSONObject;
import com.ds.lens.data.common.apmapi.ApmMetricClient;
import com.ds.lens.data.common.apmapi.enums.MetricLayerEnum;
import com.ds.lens.data.common.apmapi.in.*;
import com.ds.lens.data.common.apmapi.out.ApmMetricOutVO;
import com.ds.lens.data.common.constant.AggregatorEnum;
import com.ds.lens.data.common.query.MQLVariable;
import com.ds.lens.data.common.query.QueryAdaptor;
import com.ds.lens.data.common.query.TimeUnitEnum;
import com.ds.lens.data.common.util.HttpServiceClient;
import com.ds.lens.data.common.util.MqlParseUtil;
import com.ds.lens.data.common.util.Pair;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.ds.lens.data.common.constant.Constants.ERROR_RATIO;


public class APMQueryAdaptor implements QueryAdaptor<List<Map<String, Object>>> {

    private static final String DATASOURCE_NAME = "APM";


    private ApmMetricClient apmMetricClient;

    public APMQueryAdaptor(String url) {
        apmMetricClient = HttpServiceClient.getRestClient(ApmMetricClient.class, url);
    }

    public String getDataSourceMetaInfo() {
        return DATASOURCE_NAME;
    }

    @Override
    public List<Map<String, Object>> query(String sql, MQLVariable... variables) throws Exception {
        SimpleApmMetricInVO inVO = generateAPMQueryObject(sql);
        if (inVO != null) {
            ApmMetricOutVO<List<Map<String, Object>>> outVO = apmMetricClient.getMetricMetadataFromSimple(inVO);
            return outVO.getData();
        } else {
            return null;
        }
    }

    private SimpleApmMetricInVO generateAPMQueryObject(String sql) {
        SimpleApmMetricInVO vo = null;
        SQLStatement statement = null;
        try {
            statement = SQLUtils.parseSingleStatement(sql, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (statement instanceof SQLSelectStatement) {
            SQLSelectQuery sqlSelectQuery = ((SQLSelectStatement) statement).getSelect().getQuery();
            if (sqlSelectQuery instanceof SQLSelectQueryBlock) {
                try {
                    SQLSelectQueryBlock selectQueryBlock = (SQLSelectQueryBlock) sqlSelectQuery;
                    Pair<String, AggregatorEnum> metric = new Pair<>(null, null);
                    MqlParseUtil.parseSelectItems(metric, selectQueryBlock.getSelectList(), selectQueryBlock.getGroupBy());

                    Map<String, List<Object>> conditionMap = new HashMap<>();
                    Pair<Long, Long> timeRange = new Pair<>(null, null);
                    MqlParseUtil.parseConditions(conditionMap, timeRange, selectQueryBlock.getWhere());

                    List<String> groups = new ArrayList<>();
                    Pair<String, Long> interval = new Pair<>(null, null);
                    MqlParseUtil.parseGroupBy(groups, interval, selectQueryBlock.getGroupBy());

                    Pair<Integer, Integer> pair = new Pair<>(null, null);
                    MqlParseUtil.parseLimit(pair, selectQueryBlock.getLimit());

                    vo = SimpleApmMetricInVO.builder()
                            .metrics(getMetrics(metric, groups))
                            .range(new Range(timeRange.getLeft(), timeRange.getRight()))
                            .condition(getConditions(conditionMap))
                            .interval(interval.getRight())
                            .group(groups)
                            .groupSize(pair.getRight())
                            .build();
                } catch (Exception e) {
                    throw new RuntimeException(sql + " cannot convert to apm query object", e);
                }
            }
        }
        if (vo == null) {
            throw new RuntimeException(sql + " cannot convert to apm query object");
        } else {
            return vo;
        }
    }


    private static List<Condition> getConditions(Map<String, List<Object>> conditionMap) {
        List<Condition> list = new ArrayList<>();
        for (String key : conditionMap.keySet()) {
            list.add(new Condition(key, conditionMap.get(key)));
        }
        return list;
    }

    private static Metrics getMetrics(Pair<String, AggregatorEnum> metric, List<String> groupBys) {
        Metrics metrics = new Metrics();

        if (groupBys.contains("instanceName")) {
            metrics.setLayer(MetricLayerEnum.INSTANCE_REQUEST.getValue());
        } else {
            metrics.setLayer(MetricLayerEnum.TIER_REQUEST.getValue());
        }

        if (ERROR_RATIO.equals(metric.getLeft())) {
            metrics.setErrorRatio(metric.getLeft());
        } else {
            switch (metric.getRight()) {
                case AVG:
                    metrics.setAvg(metric.getLeft());
                    break;
                case SUM:
                    metrics.setSum(metric.getLeft());
                    break;
                case COUNT:
                    metrics.setCount(metric.getLeft());
                    break;
                case MIN:
                    metrics.setMin(metric.getLeft());
                    break;
                case MAX:
                    metrics.setMax(metric.getLeft());
                    break;
                case P50:
                case P90:
                case P95:
                case P99:
                    double percent = Double.parseDouble(metric.getRight().name().substring(1));
                    metrics.setPercentiles(new Percentiles((metric.getRight().name()), percent));
                    break;
                default:
                    //do nothing
            }
        }
        return metrics;
    }

    public static void main(String[] args) {
        String sql = "select AVG(test_lyw), env from 3 where env = 'lyw_mac_pro4' and ${query_timestamp} < datetime('m') and ${query_timestamp} >= datetime('-30m') group by env, date_histogram(${interval_timestamp}, '10m')";
        SQLStatement statement = SQLUtils.parseSingleStatement(sql, null);
        if (statement instanceof SQLSelectStatement) {
            SQLSelectQuery sqlSelectQuery = ((SQLSelectStatement) statement).getSelect().getQuery();
            if (sqlSelectQuery instanceof SQLSelectQueryBlock) {

                try {
                    SQLSelectQueryBlock selectQueryBlock = (SQLSelectQueryBlock) sqlSelectQuery;
                    Pair<String, AggregatorEnum> metric = new Pair<>(null, null);
                    MqlParseUtil.parseSelectItems(metric, selectQueryBlock.getSelectList(), selectQueryBlock.getGroupBy());

                    Map<String, List<Object>> conditionMap = new HashMap<>();
                    Pair<Long, Long> timeRange = new Pair<>(null, null);
                    MqlParseUtil.parseConditions(conditionMap, timeRange, selectQueryBlock.getWhere());

                    List<String> groups = new ArrayList<>();
                    Pair<String, Long> interval = new Pair<>(null, null);
                    MqlParseUtil.parseGroupBy(groups, interval, selectQueryBlock.getGroupBy());

                    SimpleApmMetricInVO vo = SimpleApmMetricInVO.builder()
                            .metrics(getMetrics(metric, groups))
                            .range(new Range(timeRange.getLeft(), timeRange.getRight()))
                            .condition(getConditions(conditionMap))
                            .interval(interval.getRight())
                            .group(groups)
                            .build();
                    System.out.println(JSONObject.toJSONString(vo));
                } catch (Exception e) {
                    throw new RuntimeException(sql + " cannot convert to apm query object", e);
                }

            }
        }
        System.out.println("done");

        TimeUnit unit = TimeUnit.MINUTES;
        long cur = System.currentTimeMillis();
        System.out.println(cur + " vs " + unit.toMinutes(cur));

        String param = "-30m";
        char u = param.charAt(param.length() - 1);
        long duration = Long.valueOf(param.substring(0, param.length() - 1));
        TimeUnitEnum unitEnum = TimeUnitEnum.get(u);
        cur = System.currentTimeMillis();
        long value = cur / 60000 * 60000 + unitEnum.toMills(duration) - 3 * 60000;
        System.out.println(cur + " " + value);

    }

    @Override
    public void connect() throws Exception {

    }

    @Override
    public void close() throws Exception {

    }
}
